﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using AmiEmuLib.Native;
using AmiEmuLib.Configuration.EventBindingConfig;
using System.Diagnostics;
using System.Runtime.InteropServices;
using log4net;

namespace AmiEmuLib.Host.HostInput
{
    /// <summary>
    /// Mice manager.
    /// </summary>
    public class HostMiceDevices : HostInputDevices<HostMouseDevice>
    {
        /// <summary>
        /// Logger.
        /// </summary>
        private static readonly ILog Logger = LogManager.GetLogger("AmiEmuLib.Host.Input.Mouse");

        #region Events Ids
        public const int Event_ID_Left_Button = 1;
        public const int Event_ID_Right_Button = 2;
        public const int Event_ID_Middle_Button = 3;
        public const int Event_ID_Wheel_Up = 4;
        public const int Event_ID_Wheel_Down = 5;
        public const int Event_ID_Horizontal_Wheel_Left = 6;
        public const int Event_ID_Horizontal_Wheel_Right = 7;
        public const int Event_ID_Move_Relative_X = 8;
        public const int Event_ID_Move_Relative_Y = 9;
        public const int Event_ID_Move_Absolute_X = 10;
        public const int Event_ID_Move_Absolute_Y = 11;
        public const int Event_ID_Extra_Button_4 = 12;
        public const int Event_ID_Extra_Button_5 = 13;
        public const int Event_ID_Extra_Button_6 = 14;
        public const int Event_ID_Extra_Button_32 = 40;
        public const int MAX_BUTTONS = 32;
        public const int EVENTS_COUNT = 41;
        #endregion

        /// <summary>
        /// For simpler raw input process.
        /// </summary>
        private static NativeRawInput.RawMouseButtons[] m_buttons_down = new[] { 
            NativeRawInput.RawMouseButtons.LeftDown, NativeRawInput.RawMouseButtons.RightDown, 
            NativeRawInput.RawMouseButtons.MiddleDown, NativeRawInput.RawMouseButtons.Button4Down, 
            NativeRawInput.RawMouseButtons.Button5Down };

        /// <summary>
        /// For simpler raw input process.
        /// </summary>
        private static NativeRawInput.RawMouseButtons[] m_buttons_up = new[] { 
            NativeRawInput.RawMouseButtons.LeftUp, NativeRawInput.RawMouseButtons.RightUp, 
            NativeRawInput.RawMouseButtons.MiddleUp, NativeRawInput.RawMouseButtons.Button4Up, 
            NativeRawInput.RawMouseButtons.Button5Up };

        /// <summary>
        /// For simpler raw input process.
        /// </summary>
        private static int[] m_button_event_id = new[] { Event_ID_Left_Button, 
            Event_ID_Right_Button, Event_ID_Middle_Button, Event_ID_Extra_Button_4, 
            Event_ID_Extra_Button_5 };

        /// <summary>
        /// Constructor.
        /// </summary>
        internal HostMiceDevices()
        {
        }

        /// <summary>
        /// Starts listening to WM_INPUT for all mice class devices. 
        /// </summary>
        internal override void Connect()
        {
            NativeRawInput.RawInputDevice[] rid = new NativeRawInput.RawInputDevice[1];

            rid[0].UsagePage = NativeConsts.HIDUsagePage_Generic;
            rid[0].Usage = NativeConsts.HIDUsage_Mouse;
            rid[0].Flags = NativeRawInput.RawInputDeviceFlags.InputSink;
            rid[0].WindowHandle = HostEnvironment.WindowHandle;

            if (!NativeRawInput.RegisterRawInputDevices(
                rid, 1, NativeRawInput.RawInputDevice.StructSize))
            {
                int error = Marshal.GetLastWin32Error();
                Logger.ErrorFormat("RegisterRawInputDevices() failed for mice reg (error: {0})",
                    error);
            }
        }

        /// <summary>
        /// Stop listening to WM_INPUT for all mice class devices. 
        /// </summary>
        internal override void Disconnect()
        {
            NativeRawInput.RawInputDevice[] rid = new NativeRawInput.RawInputDevice[1];

            rid[0].UsagePage = NativeConsts.HIDUsagePage_Generic;
            rid[0].Usage = NativeConsts.HIDUsage_Mouse;
            rid[0].Flags = NativeRawInput.RawInputDeviceFlags.Remove;

            if (!NativeRawInput.RegisterRawInputDevices(
                rid, 1, NativeRawInput.RawInputDevice.StructSize))
            {
                int error = Marshal.GetLastWin32Error();
                Logger.ErrorFormat("RegisterRawInputDevices() failed for mice unreg (error: {0})",
                    error);
            }
        }

        /// <summary>
        /// Process raw input events for mice. Called from main window message loop.
        /// </summary>
        /// <param name="a_focus"></param>
        /// <param name="a_ri"></param>
        internal void ProcessRawInputMessage(bool a_focus, NativeRawInput.RawInput a_ri)
        {
            NativeRawInput.RawMouse rm = a_ri.Data.Mouse;

            if (Logger.IsDebugEnabled)
            {
                Logger.DebugFormat("ButtonData: {0:X}, ButtonFlags: {1:X}, Extra: {2:X}, Flags: {3:X}, LastX: {4}, LastY: {5}, RawButtons: {6:X}",
                    rm.ButtonData, (ushort)rm.ButtonFlags, rm.ExtraInformation,
                    (ushort)rm.Flags, rm.LastX, rm.LastY, rm.RawButtons);
            }

            if ((((rm.Flags & NativeRawInput.RawMouseFlags.MoveAbsolute) != 0) ? 1 : 0) + 
                (((rm.Flags & NativeRawInput.RawMouseFlags.MoveRelative) != 0) ? 1 : 0) + 
                (((rm.Flags & NativeRawInput.RawMouseFlags.VirtualDesktop) != 0) ? 1 : 0) > 1)
            {
                Logger.ErrorFormat("Some unexcpecteed mouse flags: {0:X}", rm.Flags);
            }

            if (rm.Flags.HasFlag(NativeRawInput.RawMouseFlags.AttributesChanged))
            {
                Logger.Info("AttributesChanged arrived.");
                HostInputDevicesList.Rescan();
            }

            if (a_focus)
            {
                if ((rm.Flags == NativeRawInput.RawMouseFlags.MoveRelative) &&
                    ((rm.LastX != 0) || (rm.LastX != 0)))
                {
                    MouseMoved(rm.LastX, rm.LastY, true);
                }

                if (rm.Flags.HasFlag(NativeRawInput.RawMouseFlags.MoveAbsolute | 
                    NativeRawInput.RawMouseFlags.VirtualDesktop))
                {
                    MouseMoved(rm.LastX, rm.LastY, false);
                }

                if (rm.ButtonFlags.HasFlag(NativeRawInput.RawMouseButtons.MouseWheel))
                    MouseWheel(Event_ID_Wheel_Down, Event_ID_Wheel_Up, (short)rm.ButtonData);

                if (rm.ButtonFlags.HasFlag(NativeRawInput.RawMouseButtons.MouseHorzWheel))
                {
                    MouseWheel(Event_ID_Horizontal_Wheel_Left, Event_ID_Horizontal_Wheel_Right, 
                        (short)rm.ButtonData);
                }
            }

            for (int i = 0; i < 5; i++)
            {
                if (rm.ButtonFlags.HasFlag(m_buttons_down[i]) && a_focus)
                    ButtonStateChanged(m_button_event_id[i], true, i);
                else if (rm.ButtonFlags.HasFlag(m_buttons_up[i]) && 
                        (a_focus || m_states[m_button_event_id[i]]))
                {
                    ButtonStateChanged(m_button_event_id[i], false, i);
                }
            }

            for (int i = 5; i < 32; i++)
            {
                bool button_pushed = (rm.RawButtons & (1 << i)) != 0;
                int event_id = Event_ID_Extra_Button_6 + i - 5;

                if (button_pushed && a_focus && !m_states[event_id])
                    ButtonStateChanged(event_id, true, i);
                if (!button_pushed && m_states[event_id])
                    ButtonStateChanged(event_id, false, i);
            }
        }

        /// <summary>
        /// Mouse button state was changed.
        /// </summary>
        /// <param name="a_button_index"></param>
        /// <param name="a_pushed"></param>
        private void ButtonStateChanged(int a_event_id, bool a_pushed, int a_button_index)
        {
            m_states[a_event_id] = a_pushed;
            GenerateEvent(a_event_id, a_pushed, a_button_index);
        }

        /// <summary>
        /// Mouse wheel was changed.
        /// </summary>
        /// <param name="a_value"></param>
        private void MouseWheel(int a_event_minus, int a_event_plus, int a_value)
        {
            if (a_value > 0)
                GenerateEvent(a_event_plus, true, a_value);
            else if (a_value < 0)
                GenerateEvent(a_event_minus, true, a_value);
            else
                Logger.ErrorFormat("Unexpected wheel value, eventid: {0}", a_event_minus);
        }

        /// <summary>
        /// Mouse was moved.
        /// </summary>
        /// <param name="a_x"></param>
        /// <param name="a_y"></param>
        /// <param name="a_relative"></param>
        private void MouseMoved(int a_x, int a_y, bool a_relative)
        {
            if (a_relative)
            {
                if (a_x != 0)
                    GenerateEvent(Event_ID_Move_Relative_X, false, a_x);
                if (a_y != 0)
                    GenerateEvent(Event_ID_Move_Relative_Y, false, a_y);

                if ((a_x == 0) && (a_y == 0))
                    Logger.Error("Mouse x and y relative values set to zero.");
            }
            else
            {
                GenerateEvent(Event_ID_Move_Absolute_X, false, a_x);
                GenerateEvent(Event_ID_Move_Absolute_Y, false, a_y);
            }
        }

        protected override HostEvents GetHostEvents()
        {
            return HostEventsList.MouseEvents;
        }
    }
}
